﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Microsoft.Phone.Controls;

namespace SipCompensation
{
    internal class KeyboardPageCompensation : DependencyObject, IDisposable
    {
        private const double LANDSCAPE_SHIFT = 259d;
        private const double KEYBOARD_BAR_HEIGHT = 69d;
        private const double PORTRAIT_SHIFT = 339d;
        private const double APP_BAR_HEIGHT = 72d;

        public static readonly DependencyProperty TranslateYProperty = DependencyProperty.Register("TranslateY", typeof (double), typeof (KeyboardPageCompensation), new PropertyMetadata(default(double), PropertyChangedCallback));

        private readonly ClipboardMonitor _clipboardMonitor;

        private readonly PhoneApplicationFrame _frame;
        private readonly bool _hasOpaqueAppbar;
        private readonly string[] _keyboardsWithBar = {"Text", "Maps", "Search", "Formula", "Chat"};
        private readonly ScrollViewer _scrollViewer;
        private readonly VerticalAlignment _verticalAlignment;
        private double _currentKeyboardHeight;
        private FrameworkElement _focusedElement;
        private bool _keyboardIsOpen;
        private double _preFocusHeight;
        private TranslateTransform _translateTransform;


        public KeyboardPageCompensation(PhoneApplicationFrame frame, ScrollViewer scrollViewer)
        {
            var page = (PhoneApplicationPage) frame.Content;
            _hasOpaqueAppbar = page.ApplicationBar != null && page.ApplicationBar.Opacity < 1;

            _frame = frame;
            _scrollViewer = scrollViewer;
            _scrollViewer.SizeChanged += ScrollViewerSizeChanged;

            _verticalAlignment = _scrollViewer.VerticalAlignment;

            BindToTranslateY();

            ((PhoneApplicationPage) frame.Content).Focus();

            _focusedElement = GetFocusedElement();
            _focusedElement.LostFocus += FocusedElementOnLostFocus;

            bool startMonitor;
            try
            {
                startMonitor = !Clipboard.ContainsText();
            }
            catch (Exception)
            {
                startMonitor = true;
            }

            if (startMonitor)
            {
                _clipboardMonitor = new ClipboardMonitor();
                _clipboardMonitor.ClipboardTextChanged += ChangeScrollHeight;
            }
        }

        public void Dispose()
        {
            _scrollViewer.SizeChanged -= ScrollViewerSizeChanged;

            if (_focusedElement != null)
            {
                _focusedElement.LostFocus -= FocusedElementOnLostFocus;
            }

            if (_clipboardMonitor != null)
            {
                _clipboardMonitor.ClipboardTextChanged -= ChangeScrollHeight;
                _clipboardMonitor.Dispose();
            }

            ClearValue(TranslateYProperty);

            ResetScrollViewerHeight();
        }

        private void ChangeScrollHeight()
        {
            double keyboardHeight = GetKeyboardHeight();
            double delta = keyboardHeight - _currentKeyboardHeight;
            _currentKeyboardHeight = keyboardHeight;
            _scrollViewer.Height -= delta;
        }

        private void BindToTranslateY()
        {
            var transformGroup = _frame.RenderTransform as TransformGroup;
            if (transformGroup != null)
            {
                _translateTransform = (TranslateTransform) transformGroup.Children[0];

                var binding = new Binding("Y");
                binding.Source = _translateTransform;
                BindingOperations.SetBinding(this, TranslateYProperty, binding);
            }
        }

        private bool IsSIPOwner(FrameworkElement control)
        {
            return (control is TextBox || control is PasswordBox);
        }

        private void FocusedElementOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
        {
            FrameworkElement newFocusedElement = GetFocusedElement();
            FrameworkElement oldFocusedElement = _focusedElement;

            oldFocusedElement.LostFocus -= FocusedElementOnLostFocus;

            _focusedElement = newFocusedElement;
            _focusedElement.LostFocus += FocusedElementOnLostFocus;

            if (IsSIPOwner(newFocusedElement))
            {
                ResizePage();
            }
            else
            {
                if (IsSIPOwner(oldFocusedElement))
                {
                    ResetScrollViewerHeight();
                }
            }
        }

        private void ResetScrollViewerHeight()
        {
            if (_keyboardIsOpen)
            {
                AnimateScrollViewerHeightTo(
                    _preFocusHeight,
                    () =>
                    {
                        _scrollViewer.Height = double.NaN;
                        _scrollViewer.VerticalAlignment = _verticalAlignment;
                    });

                _keyboardIsOpen = false;
            }
        }

        private void ResizePage()
        {
            try
            {
                if (_keyboardIsOpen)
                {
                    ChangeScrollHeight();
                }
                else
                {
                    _currentKeyboardHeight = GetKeyboardHeight();
                    _preFocusHeight = _scrollViewer.ActualHeight;

                    GeneralTransform transform = _scrollViewer.TransformToVisual(_frame);
                    Point leftTopPoint = transform.Transform(new Point(0, 0));

                    double newHeight = ((FrameworkElement)_frame.Content).ActualHeight - leftTopPoint.Y - _currentKeyboardHeight;

                    _scrollViewer.VerticalAlignment = VerticalAlignment.Top;

                    AnimateScrollViewerHeightTo(newHeight);

                    _keyboardIsOpen = true;
                }
            }
            catch (ArgumentException)
            {
                // Probably scrollviewer is invisible in some cases
                // Do nothing. User probably wont even notice that SIP compensation didn't work. At least app won't crash
                // http://www.yumasoft.com/node/36
            }
        }

        private void AnimateScrollViewerHeightTo(double newHeight, Action clb = null)
        {
            var easing = new PowerEase {Power = 3, EasingMode = (newHeight > _scrollViewer.ActualHeight) ? EasingMode.EaseIn : EasingMode.EaseOut};

            var storyboard = new Storyboard();
            var doubleAnimation = new DoubleAnimation
                                  {
                                      From = _scrollViewer.ActualHeight,
                                      To = newHeight,
                                      EasingFunction = easing,
                                      Duration = new Duration(TimeSpan.FromMilliseconds(250))
                                  };
            Storyboard.SetTarget(storyboard, _scrollViewer);
            Storyboard.SetTargetProperty(storyboard, new PropertyPath("Height"));
            storyboard.Children.Add(doubleAnimation);
            storyboard.Completed += delegate { if (clb != null) clb(); };
            storyboard.Begin();
        }

        private void ScrollViewerSizeChanged(object sender, SizeChangedEventArgs e)
        {
            try
            {
                if (!IsSIPOwner(_focusedElement))
                {
                    return;
                }

                GeneralTransform generalTransform = _focusedElement.TransformToVisual((UIElement) _scrollViewer.Content);
                Point bottomPoint = generalTransform.Transform(new Point(0, _focusedElement.ActualHeight));
                Point topPoint = generalTransform.Transform(new Point(0, 0));

                double _elementScreenOffset = bottomPoint.Y - _scrollViewer.VerticalOffset;
                double _elementTopScreenOffset = topPoint.Y - _scrollViewer.VerticalOffset;

                if (_elementTopScreenOffset < 0)
                {
                    _scrollViewer.ScrollToVerticalOffset(topPoint.Y);
                }
                else
                {
                    double diff = _elementScreenOffset - e.NewSize.Height;
                    if (_hasOpaqueAppbar)
                        diff += APP_BAR_HEIGHT;

                    if (diff > 0)
                    {
                        _scrollViewer.ScrollToVerticalOffset(_scrollViewer.VerticalOffset + diff);
                    }
                }
            }
            catch (ArgumentException)
            {
                // Probably scrollviewer is invisible in some cases
                // Do nothing. User probably wont even notice that SIP compensation didn't work. At least app won't crash
                // http://www.yumasoft.com/node/36
            }
        }

        private double GetKeyboardHeight()
        {
            double keyboardHeight;
            if (_frame.Orientation == PageOrientation.PortraitUp)
            {
                keyboardHeight = PORTRAIT_SHIFT;
            }
            else
            {
                keyboardHeight = LANDSCAPE_SHIFT;
            }

            keyboardHeight += GetKeyboardBarHeight();

            return keyboardHeight;
        }

        private double GetKeyboardBarHeight()
        {
            var textBox = _focusedElement as TextBox;
            if (textBox != null)
            {
                if (textBox.InputScope != null && _keyboardsWithBar.Contains(((InputScopeName) (textBox.InputScope.Names[0])).NameValue.ToString()))
                {
                    return KEYBOARD_BAR_HEIGHT;
                }
            }

            try
            {
                if (Clipboard.ContainsText())
                {
                    return KEYBOARD_BAR_HEIGHT;
                }
            }
            catch
            {
                return 0;
            }


            return 0;
        }

        private FrameworkElement GetFocusedElement()
        {
            var focusedElement = (FrameworkElement) FocusManager.GetFocusedElement();

            if (focusedElement == null)
            {
                _scrollViewer.Focus();
                focusedElement = _scrollViewer;
            }
            return focusedElement;
        }

        private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var @this = (KeyboardPageCompensation) dependencyObject;

            Debug.WriteLine(dependencyPropertyChangedEventArgs.NewValue);

            @this._translateTransform.Y = 0;
        }
    }
}